#include "wx/wxprec.h"
#include "wx/debugrpt.h"
#include "wx/ffile.h"
#include "wx/filename.h"
#include "wx/dir.h"
#include "wx/dynlib.h"
#include "wx/xml/xml.h"
#include "wx/stackwalk.h"
#include "wx/wfstream.h"
#include "wx/zipstrm.h"

// wxQA

class XmlStackWalker : public wxStackWalker {
  public:
    XmlStackWalker( wxXmlNode *nodeStack ) {
      m_isOk = false;
      m_nodeStack = nodeStack;
    }
    bool IsOk() const { return m_isOk; }
  protected:
    virtual void OnStackFrame( const wxStackFrame& frame );
    wxXmlNode *m_nodeStack;
    bool m_isOk;
};

static inline void HexProperty( wxXmlNode *node, const wxChar *name, unsigned long value ) {
  node->AddProperty( name, wxString::Format( _T( "%08lx" ), value ) );
}

static inline void NumProperty( wxXmlNode *node, const wxChar *name, unsigned long value ) {
  node->AddProperty( name, wxString::Format( _T( "%lu" ), value ) );
}

static inline void TextElement( wxXmlNode *node, const wxChar *name, const wxString& value ) {
  wxXmlNode *nodeChild = new wxXmlNode( wxXML_ELEMENT_NODE, name );
  node->AddChild( nodeChild );
  nodeChild->AddChild( new wxXmlNode( wxXML_TEXT_NODE, wxEmptyString, value ) );
}

void XmlStackWalker::OnStackFrame( const wxStackFrame& frame ) {
  m_isOk = true;
  wxXmlNode *nodeFrame = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "架构" ) );
  m_nodeStack->AddChild( nodeFrame );
  NumProperty( nodeFrame, _T( "级别" ), frame.GetLevel() );
  wxString func = frame.GetName();
  if( !func.empty() ) {
    nodeFrame->AddProperty( _T( "函数" ), func );
    HexProperty( nodeFrame, _T( "偏移" ), frame.GetOffset() );
  }
  if( frame.HasSourceLocation() ) {
    nodeFrame->AddProperty( _T( "文件" ), frame.GetFileName() );
    NumProperty( nodeFrame, _T( "行" ), frame.GetLine() );
  }
  const size_t nParams = frame.GetParamCount();
  if( nParams ) {
    wxXmlNode *nodeParams = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "参数" ) );
    nodeFrame->AddChild( nodeParams );
    for( size_t n = 0; n < nParams; n++ ) {
      wxXmlNode *
      nodeParam = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "参数" ) );
      nodeParams->AddChild( nodeParam );
      NumProperty( nodeParam, _T( "编号" ), n );
      wxString type, name, value;
      if( !frame.GetParam( n, &type, &name, &value ) ) {
        continue;
      }
      if( !type.empty() ) {
        TextElement( nodeParam, _T( "类型" ), type );
      }
      if( !name.empty() ) {
        TextElement( nodeParam, _T( "名称" ), name );
      }
      if( !value.empty() ) {
        TextElement( nodeParam, _T( "值" ), value );
      }
    }
  }
}

wxDebugReport::wxDebugReport() {
  wxString appname = GetReportName();
  wxFileName fn;
  fn.AssignTempFileName( appname );
  m_dir.Printf( _T( "%s%c%s_dbgrpt-%lu-%s" ), fn.GetPath().c_str(), wxFILE_SEP_PATH, appname.c_str(),
                wxGetProcessId(), wxDateTime::Now().Format( _T( "%Y%m%dT%H%M%S" ) ).c_str() );
  if( !wxMkdir( m_dir, 0700 ) ) {
    wxLogSysError( _( "创建目录失败 \"%s\"" ), m_dir.c_str() );
    wxLogError( _( "无法创建调试报告." ) );
    Reset();
  }
}

wxDebugReport::~wxDebugReport() {
  if( !m_dir.empty() ) {
    wxDir dir( m_dir );
    wxString file;
    for( bool cont = dir.GetFirst( &file ); cont; cont = dir.GetNext( &file ) ) {
      if( wxRemove( wxFileName( m_dir, file ).GetFullPath() ) != 0 ) {
        wxLogSysError( _( "无法删除调试报告文件 \"%s\"" ), file.c_str() );
        m_dir.clear();
        break;
      }
    }
  }
  if( !m_dir.empty() ) {
    if( wxRmDir( m_dir.fn_str() ) != 0 ) {
      wxLogSysError( _( "清理调试报告目录失败 \"%s\"" ), m_dir.c_str() );
    }
  }
}

wxString wxDebugReport::GetReportName() const {
  if( wxTheApp ) {
    return wxTheApp->GetAppName();
  }
  return _T( "wx" );
}

void wxDebugReport::AddFile( const wxString& filename, const wxString& description ) {
  wxString name;
  wxFileName fn( filename );
  if( fn.IsAbsolute() ) {
    name = fn.GetFullName();
    wxCopyFile( fn.GetFullPath(), wxFileName( GetDirectory(), name ).GetFullPath() );
  } else {
    name = filename;
    wxASSERT_MSG( wxFileName( GetDirectory(), name ).FileExists(), _T( "文件应该存在于调试报告目录中" ) );
  }
  m_files.Add( name );
  m_descriptions.Add( description );
}

bool wxDebugReport::AddText( const wxString& filename, const wxString& text, const wxString& description ) {
  wxASSERT_MSG( !wxFileName( filename ).IsAbsolute(), _T( "文件名应相对于调试报告目录" ) );
  wxFileName fn( GetDirectory(), filename );
  wxFFile file( fn.GetFullPath(), _T( "w" ) );
  if( !file.IsOpened() || !file.Write( text ) ) {
    return false;
  }
  AddFile( filename, description );
  return true;
}

void wxDebugReport::RemoveFile( const wxString& name ) {
  const int n = m_files.Index( name );
  wxCHECK_RET( n != wxNOT_FOUND, _T( "没有这样的文件 wxDebugReport" ) );
  m_files.RemoveAt( n );
  m_descriptions.RemoveAt( n );
  wxRemove( wxFileName( GetDirectory(), name ).GetFullPath() );
}

bool wxDebugReport::GetFile( size_t n, wxString *name, wxString *desc ) const {
  if( n >= m_files.GetCount() ) {
    return false;
  }
  if( name ) {
    *name = m_files[n];
  }
  if( desc ) {
    *desc = m_descriptions[n];
  }
  return true;
}

void wxDebugReport::AddAll( Context context ) {
  AddContext( context );
}

bool wxDebugReport::DoAddSystemInfo( wxXmlNode *nodeSystemInfo ) {
  nodeSystemInfo->AddProperty( _T( "描述" ), wxGetOsDescription() );
  return true;
}

bool wxDebugReport::DoAddLoadedModules( wxXmlNode *nodeModules ) {
  wxDynamicLibraryDetailsArray modules( wxDynamicLibrary::ListLoaded() );
  const size_t count = modules.GetCount();
  if( !count ) {
    return false;
  }
  for( size_t n = 0; n < count; n++ ) {
    const wxDynamicLibraryDetails& info = modules[n];
    wxXmlNode *nodeModule = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "模块" ) );
    nodeModules->AddChild( nodeModule );
    wxString path = info.GetPath();
    if( path.empty() ) {
      path = info.GetName();
    }
    if( !path.empty() ) {
      nodeModule->AddProperty( _T( "路径" ), path );
    }
    void *addr = NULL;
    size_t len = 0;
    if( info.GetAddress( &addr, &len ) ) {
      HexProperty( nodeModule, _T( "地址" ), wxPtrToUInt( addr ) );
      HexProperty( nodeModule, _T( "尺寸" ), len );
    }
    wxString ver = info.GetVersion();
    if( !ver.empty() ) {
      nodeModule->AddProperty( _T( "版本" ), ver );
    }
  }
  return true;
}

bool wxDebugReport::DoAddExceptionInfo( wxXmlNode *nodeContext ) {
  wxUnusedVar( nodeContext );
  return false;
}

bool wxDebugReport::AddContext( wxDebugReport::Context ctx ) {
  wxCHECK_MSG( IsOk(), false, _T( "use IsOk() first" ) );
  wxXmlDocument xmldoc;
  wxXmlNode *nodeRoot = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "报告" ) );
  xmldoc.SetRoot( nodeRoot );
  nodeRoot->AddProperty( _T( "类型" ), ctx == Context_Current ? _T( "用户" ) : _T( "异常" ) );
  wxXmlNode *nodeSystemInfo = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "系统" ) );
  if( DoAddSystemInfo( nodeSystemInfo ) ) {
    nodeRoot->AddChild( nodeSystemInfo );
  } else { delete nodeSystemInfo; }
  wxXmlNode *nodeModules = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "模块" ) );
  if( DoAddLoadedModules( nodeModules ) ) {
    nodeRoot->AddChild( nodeModules );
  } else { delete nodeModules; }
  if( ctx == Context_Exception ) {
    wxXmlNode *nodeContext = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "语境" ) );
    if( DoAddExceptionInfo( nodeContext ) ) {
      nodeRoot->AddChild( nodeContext );
    } else { delete nodeContext; }
  }
  wxXmlNode *nodeStack = new wxXmlNode( wxXML_ELEMENT_NODE, _T( "堆栈" ) );
  XmlStackWalker sw( nodeStack );
  if( ctx == Context_Exception ) {
    sw.WalkFromException();
  } else { sw.Walk(); }
  if( sw.IsOk() ) {
    nodeRoot->AddChild( nodeStack );
  } else { delete nodeStack; }
  DoAddCustomContext( nodeRoot );
  wxFileName fn( m_dir, GetReportName(), _T( "xml" ) );
  if( !xmldoc.Save( fn.GetFullPath() ) ) {
    return false;
  }
  AddFile( fn.GetFullName(), _( "进程上下文描述" ) );
  return true;
}

bool wxDebugReport::Process() {
  if( !GetFilesCount() ) {
    wxLogError( _( "生成调试报告失败." ) );
    return false;
  }
  if( !DoProcess() ) {
    wxLogError( _( "处理调试报告失败，将文件留在 \"%s\" 目录." ), GetDirectory().c_str() );
    Reset();
    return false;
  }
  return true;
}

bool wxDebugReport::DoProcess() {
  wxString msg( _( "已生成调试报告. 它可以在" ) );
  msg << _T( "\n" )
      _T( "\t" ) << GetDirectory() << _T( "\n\n" )
      << _( "包括以下文件:\n" );
  wxString name, desc;
  const size_t count = GetFilesCount();
  for( size_t n = 0; n < count; n++ ) {
    GetFile( n, &name, &desc );
    msg += wxString::Format( _( "\t%s: %s\n" ), name.c_str(), desc.c_str() );
  }
  msg += _( "\n请将此报告发送给程序维护人员, 谢谢您!\n" );
  wxLogMessage( _T( "%s" ), msg.c_str() );
  Reset();
  return true;
}

bool wxDebugReportCompress::DoProcess() {
  const size_t count = GetFilesCount();
  if( !count ) {
    return false;
  }
  wxFileName fn( GetDirectory(), GetReportName(), _T( "zip" ) );
  wxFFileOutputStream os( fn.GetFullPath(), _T( "wb" ) );
  wxZipOutputStream zos( os, 9 );
  wxString name, desc;
  for( size_t n = 0; n < count; n++ ) {
    GetFile( n, &name, &desc );
    wxZipEntry *ze = new wxZipEntry( name );
    ze->SetComment( desc );
    if( !zos.PutNextEntry( ze ) ) {
      return false;
    }
    wxFileName filename( fn.GetPath(), name );
    wxFFileInputStream is( filename.GetFullPath() );
    if( !is.IsOk() || !zos.Write( is ).IsOk() ) {
      return false;
    }
  }
  if( !zos.Close() ) {
    return false;
  }
  m_zipfile = fn.GetFullPath();
  return true;
}

wxDebugReportUpload::wxDebugReportUpload( const wxString& url, const wxString& input, const wxString& action, const wxString& curl )
  : m_uploadURL( url ),
    m_inputField( input ),
    m_curlCmd( curl ) {
  if( m_uploadURL.Last() != _T( '/' ) ) {
    m_uploadURL += _T( '/' );
  }
  m_uploadURL += action;
}

bool wxDebugReportUpload::DoProcess() {
  if( !wxDebugReportCompress::DoProcess() ) {
    return false;
  }
  wxArrayString output, errors;
  int rc = wxExecute( wxString::Format( _T( "%s -F %s=@\"%s\" %s" ), m_curlCmd.c_str(), m_inputField.c_str(),
                                        GetCompressedFileName().c_str(), m_uploadURL.c_str() ), output, errors );
  if( rc == -1 ) {
    wxLogError( _( "无法执行curl，请将其安装到 PATH." ) );
  } else if( rc != 0 ) {
    const size_t count = errors.GetCount();
    if( count ) {
      for( size_t n = 0; n < count; n++ ) {
        wxLogWarning( _T( "%s" ), errors[n].c_str() );
      }
    }
    wxLogError( _( "未能上载调试报告 (错误代码 %d)." ), rc );
  } else {
    if( OnServerReply( output ) ) {
      return true;
    }
  }
  return false;
}
